////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef __WN_CONTAINERS_CALLBACK_H__
#define __WN_CONTAINERS_CALLBACK_H__

//NOTE callbacks leak memory.. should find out some way to fix this

template<typename R, typename P>
class WNCallbackBase1{
public:
    WNCallbackBase1(): mParam(0) {}
    virtual R Execute(P _param) const = 0;
    WN_VOID AddRef() {
        WNAtomicIncrement(&mParam);
    }
    WN_ATOM_T RemoveRef() {
        WNAtomicDecrement(&mParam);
        return(mParam);
    }
private:
    WN_ATOM_T mParam;
};

template <typename T, typename R, typename P>
class WNCallbackInternal1: public WNCallbackBase1<R,P> {
public:
    WNCallbackInternal1(R (T::*_function)(P), T* _this): 
        WNCallbackBase1(),
        mThis(_this),
        mFunction(_function){
    }
    virtual R Execute(P _param) const {
        WN_RELEASE_ASSERT_DESC(mThis != WN_NULL, "This pointer is null");
        WN_RELEASE_ASSERT_DESC(mFunction != WN_NULL, "Invalid function pointer");
        return(mThis->mFunction(_param));
    }
private:
    T* mThis;
    R (T::*mFunction)(P);
};

template <typename R, typename P>
class WNCallbackClasslessInternal1: public WNCallbackBase1<R,P> {
public:
    WNCallbackClasslessInternal1(R (*_function)(P)):
        WNCallbackBase1(),
        mFunction(_function){
    }
    virtual R Execute(P _param) const {
        WN_RELEASE_ASSERT_DESC(mFunction != WN_NULL, "Invalid function pointer");
        return(mFunction(_param));
    }
private:
    R (*mFunction)(P);
};

template <typename R, typename P>
class WNCallback1 {
public:
    template<typename T, typename R, typename P>
    WNCallback1(T* _this, R (T::*_func(P))){
        mBaseCallback = WN_CHECKED_NEW WNCallbackInternal1<T, R, P>(_this, _func);
        if(mBaseCallback) {
            mBaseCallback->AddRef();
        }
    }
    template<typename R, typename P>
    WNCallback1(R (*_func)(P)){
        mBaseCallback = WN_CHECKED_NEW WNCallbackClasslessInternal1<R, P>(_func);
        if(mBaseCallback) {
            mBaseCallback->AddRef();
        }
    }
    WNCallback1() {
        mBaseCallback = WN_NULL;
    }

    ~WNCallback1() {
        if(mBaseCallback) {
            if(mBaseCallback->RemoveRef() == 0) {
                WN_DELETE(mBaseCallback);
            }
        }
    }

    WNCallback1(const WNCallback1& other) {
        mBaseCallback = other.mBaseCallback;
        if(mBaseCallback) {
            mBaseCallback->AddRef();
        }
    }

    R Execute(P _param1) const {
        WN_DEBUG_ASSERT(mBaseCallback != WN_NULL);
        return(mBaseCallback->Execute(_param1));
    }

private:
    WNCallbackBase1<R, P>* mBaseCallback;
};

template<typename R, typename P, typename P2>
class WNCallbackBase2{
public:
    WNCallbackBase2(): mParam(0) {}
    virtual R Execute(P _param, P2 _param2) const = 0;

    WN_VOID AddRef() {
        WNAtomicIncrement(&mParam);
    }
    WN_ATOM_T RemoveRef() {
        WNAtomicDecrement(&mParam);
        return(mParam);
    }
private:
    WN_ATOM_T mParam;
};

template <typename T, typename R, typename P, typename P2>
class WNCallbackInternal2: public WNCallbackBase2<R,P, P2> {
public:
    WNCallbackInternal2(R (T::*_function)(P, P2), T* _this):
            WNCallbackBase2(),
            mThis(_this),
            mFunction(_function){
      }
      virtual R Execute(P _param, P2 _param2) const {
          WN_RELEASE_ASSERT_DESC(mThis != WN_NULL, "This pointer is null");
          WN_RELEASE_ASSERT_DESC(mFunction != WN_NULL, "Invalid function pointer");
          return(mThis->mFunction(_param, _param2));
      }
private:
    T* mThis;
    R (T::*mFunction)(P, P2);
};

template <typename R, typename P, typename P2>
class WNCallbackClasslessInternal2: public WNCallbackBase2<R,P, P2> {
public:
    WNCallbackClasslessInternal2(R (*_function)(P, P2)):
      WNCallbackBase2(),
      mFunction(_function){
      }
      virtual R Execute(P _param, P2 _param2) const {
          WN_RELEASE_ASSERT_DESC(mFunction != WN_NULL, "Invalid function pointer");
          return(mFunction(_param, _param2));
      }
private:
    R (*mFunction)(P, P2);
};

template <typename R, typename P, typename P2>
class WNCallback2 {
public:
    template<typename T, typename R, typename P, typename P2>
    WNCallback2(T* _this, R (T::*_func(P, P2))){
        mBaseCallback = new WNCallbackInternal1<T, R, P, P2>(_this, _func);
        if(mBaseCallback) {
            mBaseCallback->AddRef();
        }
    }
    template<typename R, typename P, typename P2>
    WNCallback2(R (*_func)(P, P2)){
        mBaseCallback = new WNCallbackClasslessInternal2<R, P, P2>(_func);
        if(mBaseCallback) {
            mBaseCallback->AddRef();
        }
    }

    WNCallback2() {
        mBaseCallback = WN_NULL;
    }

    WNCallback2(const WNCallback2& other) {
        mBaseCallback = other.mBaseCallback;
        if(mBaseCallback) {
            mBaseCallback->AddRef();
        }
    }

    WNCallback2<R, P, P2>& operator=(const WNCallback2& other) {
        if(mBaseCallback) {
            if(mBaseCallback->RemoveRef() == 0) {
                WN_DELETE(mBaseCallback);
            }
        }
        mBaseCallback = other.mBaseCallback;
        if(mBaseCallback) {
            mBaseCallback->AddRef();
        }
        return(*this);
    }

    ~WNCallback2() {
        if(mBaseCallback) {
            if(mBaseCallback->RemoveRef() == 0) {
                WN_DELETE(mBaseCallback);
            }
        }
    }

    R Execute(P _param1, P2 _param2) const {
        WN_DEBUG_ASSERT(mBaseCallback != WN_NULL);
        return(mBaseCallback->Execute(_param1, _param2));
    }

private:
    WNCallbackBase2<R, P, P2>* mBaseCallback;
};

#endif // __WN_CONTAINERS_CALLBACK_H__